Importando os pacotes e funções

source('misc/packages.R')
source('misc/functions.R')

EDA do dataset da Olist de e-commerce no Brasil

Este conjunto de dados é uma representação de informações detalhadas sobre 100 mil pedidos feitos na Olist Store durante o período de 2016 a 2018, abrangendo diversas plataformas de venda no Brasil. Os recursos presentes neste conjunto de dados nos permitem explorar os pedidos sob diversas perspectivas, desde o status do pedido, preço, pagamento e desempenho de frete, até a localização do cliente, atributos dos produtos e, por fim, avaliações escritas pelos clientes. É importante ressaltar que esses dados comerciais são reais, embora tenham sido anonimizados.

Esse conjunto de dados foi gentilmente fornecido pela Olist, a maior loja de departamentos em marketplaces brasileiros. A Olist conecta pequenos negócios de todo o Brasil a canais de venda de forma simplificada, com um único contrato. Esses comerciantes têm a capacidade de vender seus produtos através da Olist Store e enviá-los diretamente aos clientes utilizando os parceiros logísticos da Olist.

No sistema da Olist cada pedido é designado a um customerid único. Isso significa que cada consumidor terá diferentes ids para diferentes pedidos. O propósito de ter um customerunique_id único por pessoa (como se fosse um CPF) na base é permitir identificar consumidores que fizeram recompras na loja. Caso contrário, você encontraria que cada ordem sempre tivesse diferentes consumidores associados.

customers <- read_csv('data/olist_customers_dataset.csv')
orders <- read_csv('data/olist_orders_dataset.csv')
reviews <- read_csv('data/olist_order_reviews_dataset.csv')
payments <- read_csv('data/olist_order_payments_dataset.csv')
products <- read_csv('data/olist_products_dataset.csv')
sellers <- read_csv('data/olist_sellers_dataset.csv')
categories <- read_csv('data/product_category_name_translation.csv')
geolocation <- read_csv('data/olist_geolocation_dataset.csv')
order_items <- read_csv('data/olist_order_items_dataset.csv')

# Juntando os dados em uma base única com exceção dos dados de geolocalização
complete_data <- customers %>% 
  left_join(orders) %>% 
  left_join(order_items) %>% # Uma ordem pode ter muito itens e por isso a base expande aqui
  left_join(reviews) %>% 
  left_join(payments) %>% 
  left_join(products) %>%
  left_join(sellers) %>% 
  left_join(categories)

Primeiras observações nos dados

skim(complete_data)
── Data Summary ────────────────────────
                           Values       
Name                       complete_data
Number of rows             119143       
Number of columns          40           
_______________________                 
Column type frequency:                  
  character                18           
  numeric                  14           
  POSIXct                  8            
________________________                
Group variables            None         

A análise por item expande a base, mas a maioria dos pedidos (87.6%) consiste em apenas 1 item, como já era de se esperar. Para simplificar, vamos limitar nossa análise somente a pedidos com um item.

order_items %>% 
  count(order_item_id) %>% # Número de itens do mesmo pedido
  mutate(prop = round(n / sum(n) * 100, 2))

complete_data <- complete_data %>% 
  filter(order_item_id == 1)

Transformando os dados

Nesta etapa, após a análise inicial da estrutura dos dados, criamos variáveis que serão úteis na análise e reduzimos o escopo da análise somente para entregas concluídas (order_status == ‘delivered’).

  • Variável de review alto: “1” se score do review for 5, e “0” caso contrário.

  • Variável de diferença no prazo de entrega: diferença entre o prazo de entrega estipulado para o cliente e a data de entrega propriamente dita.

  • Variável de proporção do frete: relaciona valores de Frete e Preço.

  • Variável de frete gratuito: binária

analysis <- complete_data %>% 
  mutate(high_review = ifelse(review_score == 5, 'Max. Score', 'Less than 5'),
         high_review_binary = ifelse(review_score == 5, 1, 0),
         order_delivered_customer_date = as.Date(order_delivered_customer_date),
         order_estimated_delivery_date = as.Date(order_estimated_delivery_date),
         delivery_delay = as.numeric(order_delivered_customer_date - order_estimated_delivery_date),
         late = ifelse(delivery_delay > 0, 1, 0),
         freight_prop = freight_value / price,
         free_freight = ifelse(freight_value == 0, 1, 0)) %>% 
  select(customer_state, 
         order_status, 
         price, 
         freight_value, 
         payment_type, 
         payment_value, 
         product_category_name,
         product_photos_qty,
         review_score,
         high_review,
         high_review_binary,
         delivery_delay,
         late,
         order_estimated_delivery_date,
         order_delivered_customer_date,
         review_comment_title,
         review_comment_message,
         freight_prop,
         free_freight,
         customer_city,
         seller_city) %>% 
  filter(order_status == 'delivered')

Também precisamos entender a distribuição dos dados faltantes no dataset, por isso usaremos o pacote vis_dat. Como nossa base de dados é muito grande, vamos pegar uma parte menor (30% do total) e tentar entender quais colunas possuem muitos dados faltando.

Naturalmente, as colunas com maior quantidade de dados faltantes são as que possuem preenchimento opcional: título e mensagem do comentário de revisão do produto.

set.seed(2101)
vis_dat(analysis %>% sample_frac(0.3))

map_df(analysis, ~sum(is.na(.))) %>% 
  gather() %>% 
  arrange(desc(value))

Outro fato que precisamos corrigir antes de qualquer análise é a existência de valores nulos na coluna “late”, que é uma coluna binária indicando a presença ou ausência de um atraso na entrega. Isso ocorre porque alguns pedidos não possuem data de entrega. Por isso iremos remover essas ocorrências.

analysis <- analysis %>% 
  drop_na(late)

Por último, vamos checar as cidades com mais pedidos entregues.

analysis %>% 
  count(customer_city) %>%
  mutate(prop = round(n / sum(n) * 100, 2)) %>%
  arrange(desc(n))

Exploração e visualização de variáveis

Avaliação do produto (1 a 5)

analysis %>%
  count(review_score) %>%
  ggplot(aes(x = review_score, y = n)) +
  geom_bar(stat ='identity') +
  geom_text(aes(label = n), vjust = -0.5, size = 3)

Nota máxima: sim ou não

analysis %>%
  count(high_review) %>%
  ggplot(aes(x = high_review, y = n)) +
  geom_bar(stat ='identity') +
  geom_text(aes(label = n), vjust = -0.5, size = 3)

Pedidos por UF

analysis %>%
  count(customer_state) %>%
  ggplot(aes(x = customer_state, y = n)) +
  geom_bar(stat ='identity') +
  geom_text(aes(label = n), vjust = -0.5, size = 3)

Pedidos por forma de pagamento

analysis %>%
  count(payment_type) %>%
  ggplot(aes(x = payment_type, y = n)) +
  geom_bar(stat ='identity') +
  geom_text(aes(label = n), vjust = -0.5, size = 3)

Pedidos por categoria do produto

analysis %>% 
  count(product_category_name, sort = T) %>% 
  mutate(prop = round(n / sum(n) * 100, 2))

Análise de preços

summary(analysis$price)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.85   41.00   79.00  125.17  139.00 6735.00 
p1 <- analysis %>% 
  ggplot(aes(y = price)) +
  geom_boxplot() +
  ggtitle('Boxplot')

p2 <- analysis %>% 
  ggplot(aes(x = price)) +
  geom_density() +
  ggtitle('Densidade')

grid.arrange(p1, p2, nrow = 1)

Essas análises nos informam que a variável Preço é extremamente assimétrica, inclusive com outliers com preços acima de R$ 6000. O mais comum aqui, caso seja desejável incluí-la na modelagem, seria transformar essa variável e estabilizá-la. Por isso, a partir de agora usaremos o logaritmo da variável Preço.

Lembrando que o Boxplot pode ser problemático por não refletir a distribuição dos dados. Veremos adiante um exemplo melhor, com Violin Plots.

p1log <- analysis %>% 
  ggplot(aes(y = log(price))) +
  geom_boxplot() +
  ggtitle('Boxplot')

p2log <- analysis %>% 
  ggplot(aes(x = log(price))) +
  geom_density() +
  ggtitle('Densidade')

grid.arrange(p1log, p2log, nrow = 1)

Análise de frete

summary(analysis$freight_value)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.00   13.32   16.39   20.20   21.23  409.68 

75% dos produtos tiveram um frete inferior a 21 reais, mas é possível ver que um pedido em específico teve um frete superior a 400 reais!

analysis %>% 
  ggplot(aes(x = log(freight_value))) +
  geom_density() +
  ggtitle('Variável Frete com Transformação Logarítmica')

A variável de Frete parece ser mais problemática, pois tem valores 0 e alguns saltos. A melhor maneira de identificar rapidamente onde estão esses pontos de corte é através de interatividade gráfica!

(analysis %>% 
  filter(freight_value > 0) %>% # Tira os fretes gratuitos
  ggplot(aes(x = freight_value)) +
  geom_density()) %>% ggplotly()

O ponto no entorno do 10 parece um ponto de atenção pela subida íngreme após ele. Temos muitos valores de frete repetidos. Talvez seria interessante transformar essa variável em categórica.

Além disso, o frete deve ter relação com a distância entre cliente e vendedor, e uma análise futura poderia levar isso em consideração fazendo algum tipo de manipulação de localização, mas talvez os dados não permitam fazer essa análise de maneira fácil.

Análise de frete sobre preço

summary(analysis$freight_prop)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0000  0.1320  0.2249  0.3110  0.3836 21.4471 

Aqui mais uma vez vemos uma proporção absurda de frete sobre preço. Em um pedido específico, o frete correspondeu a 21.4% do valor total da compra.

analysis %>% 
  ggplot(aes(x = log(freight_prop))) +
  geom_density()

O logaritmo dessa variável, quando o frete é zero, resulta em -Infinito. Isso causaria problemas na hora de criar os modelos, mas podemos resolver facilmente: basta acrescentar um valor irrisório ao frete, de modo que a divisão não resulte em zero.

Análise de frete x preço

analysis %>% 
  ggplot(aes(x = log(price),
             y = log(freight_value))) +
  geom_point() +
  geom_smooth()

Fretes gratuitos

analysis %>% 
  count(free_freight, sort = T) %>% 
  mutate(prop = n / sum(n))

Os pedidos com frete gratuito equivalem a menos de 0,4% do total. É uma porção quase irrelevante dos dados, então podemos considerar remover esses dados para facilitar a modelagem futura.

Análise de atrasos nas entregas

summary(as.numeric(analysis$delivery_delay))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 -147.0   -17.0   -12.0   -11.9    -7.0   188.0 
analysis %>% 
  count(late) %>% 
  mutate(prop = n / sum(n))

Podemos perceber que 75% dos pedidos chegaram com no mínimo 7 dias de antecedência, mas existem também pedidos com atrasos absurdos, chegando a 6 meses. Também descobrimos que menos de 7% dos pedidos analisados foram entregues com atraso.

analysis %>% 
  ggplot(aes(x = delivery_delay)) +
  geom_density()

Aqui podemos ver uma leve inclinação a partir do 0, indicando os pedidos atrasados. O inclínio é bem pequeno e já sabemos que houveram relativamente poucos pedidos atrasados.

Quantidade de fotos do produto

Muitos produtos em marketplaces costumam contar com fotos ilustrativas do produto, que ajudam a guiar o cliente na hora da compra.

analysis %>% 
  count(product_photos_qty) %>% 
  mutate(prop = n / sum(n))

Múltiplas densidades Preço vs. Categoria

Vamos analisar a densidade do preço por categoria. Novamente usamos fct_lump para agrupar as categorias fora do top 7 em frequência e criamos um gráfico com uma curva de densidade para o preço vs. cada categoria. Como as curvas se sobrepõem, a visualização é dificultada.

analysis %>% 
  mutate(cat_product_category_name = fct_lump(product_category_name, n = 7)) %>% 
  ggplot(aes(x = log(price), fill = cat_product_category_name)) +
  geom_density(alpha = 0.35)

Um modo melhor de visualizar seria colocar a base de cada curva em um nível diferente. O gráfico acima nos mostrava a existência de NAs na coluna, então vamos também remover.

analysis %>%
  filter(!is.na(product_category_name)) %>% 
  mutate(cat_product_category_name = fct_lump(product_category_name, n = 7),
         log_price = log(price)) %>%
  ggplot(aes(x = log_price, 
             y = cat_product_category_name,
             fill = stat(x))) + 
  geom_density_ridges_gradient(scale = 3, rel_min_height = 0.001) +
  scale_fill_viridis_c(name = "Log do Preço")

Outro modo de analisar os valores por categoria seria criando um boxplot para cada, sabendo da limitação natural do boxplot em não refletir bem a distribuição/frequência dos dados.

analysis %>% 
  filter(!is.na(product_category_name)) %>%
  mutate(cat_product_category_name = fct_lump(product_category_name, n = 7),
         log_price = log(price)) %>% 
  ggplot(aes(fill = cat_product_category_name,
             y = log_price,
             x = cat_product_category_name)) +
  geom_boxplot() +
  theme(legend.title = element_blank(),
        axis.text.x = element_text(angle = 45))

Com algumas mudanças no código anterior, podemos transformar os box plots em violin plots, que mostram com clareza a distribuição dos dados. A limitação natural do violin plot no ggplot em R é a ausência das linhas percentis, então podemos adicionar um box plot dentro de cada violin plot e obter o “melhor dos dois mundos”

analysis %>%
  filter(!is.na(product_category_name)) %>%
  mutate(cat_product_category_name = fct_lump(product_category_name, n = 7),
         log_price = log(price)) %>% 
  ggplot(aes(fill = cat_product_category_name,
             y = log_price,
             x = cat_product_category_name)) +
  geom_violin(trim = FALSE) +  # trim = FALSE shows the full distribution
  geom_boxplot(width = 0.2, outlier.shape = NA, position = position_dodge(width = 0.75)) +
  theme(legend.title = element_blank(),
        axis.text.x = element_text(angle = 45))

Análises bivariadas

Estado vs. Avaliação do cliente

analysis %>%
  filter(!is.na(high_review_binary)) %>%
  group_by(customer_state) %>%
  summarize(
    n = n(),
    max_reviews = sum(high_review_binary),
    max_reviews_prop = mean(high_review_binary)) %>%
  arrange(desc(max_reviews_prop))

Mesmo São Paulo sendo de maneira disparada o estado com mais pedidos, também é o estado mais satisfeito: 61.7% dos pedidos foram avaliados com nota máxima. Vamos agora testar uma visualização interativa com Plotly que nos permitirá ver exatamente o número de pedidos por estado e a “taxa de satisfação” de cada.

Aqui vemos que o estado mais insatisfeito seria o Maranhão, onde apenas 48.3% dos 739 pedidos foram avaliados com a nota máxima. De qualquer maneira, não é possível traçar um paralelo entre estado e probabilidade de avaliação máxima.

analysis %>% 
  plota_tx_interesse(var_x = 'customer_state',
                     var_y = 'high_review',
                     flag_interesse = 'Max. Score')

Vamos tentar mudar a escala da visualização para obter maior clareza.

analysis %>% 
  plota_tx_interesse(var_x = 'customer_state',
                     var_y = 'high_review',
                     flag_interesse = 'Max. Score',
                     ylim = NA)

Forma de pagamento vs. Avaliação do cliente

analysis %>% 
  plota_tx_interesse(var_x = 'payment_type',
                     var_y = 'high_review',
                     flag_interesse = 'Max. Score',
                     ylim = NA)

Categoria do produto vs. Avaliação do cliente

analysis %>% 
  plota_tx_interesse(var_x = 'product_category_name',
                     var_y = 'high_review',
                     flag_interesse = 'Max. Score',
                     ylim = NA)

Temos muitas classes diferentes e isso acaba complicando muito a visualização dos dados. Por isso vamos pegar as 10 mais presentes e agrupar o resto em uma 11ª variável, chamada de “Other”. Isso é possível através da função fct_lump a seguir

analysis %>% 
  mutate(product_category_name_cat = fct_lump(product_category_name, 10)) %>% 
  plota_tx_interesse(var_x = 'product_category_name_cat',
                     var_y = 'high_review',
                     flag_interesse = 'Max. Score',
                     ylim = NA)

Agora com o gráfico muito mais legível, podemos perceber rapidamente que as 3 categorias com maior taxa de satisfação do cliente (avaliação 5 estrelas) são:

  • Brinquedos (62.8%)

  • Beleza e saúde (62.1%)

  • Esporte e lazer (61.4%)

Preço vs. Avaliação do cliente

analysis %>% 
  group_by(high_review) %>% 
  summarize(n = n(),
            mean = mean(price, na.rm = T),
            sd = sd(price, na.rm = T),
            min = min(price, na.rm = T),
            max = max(price, na.rm = T))

Frete vs. Avaliação do cliente

analysis %>% 
  group_by(high_review) %>% 
  summarize(n = n(),
            mean = mean(freight_value, na.rm = T),
            sd = sd(freight_value, na.rm = T),
            min = min(freight_value, na.rm = T),
            max = max(freight_value, na.rm = T))

Frete sobre preço vs. Avaliação do cliente

analysis %>% 
  group_by(high_review) %>% 
  summarize(n = n(),
            mean = mean(freight_prop, na.rm = T),
            sd = sd(freight_prop, na.rm = T),
            min = min(freight_prop, na.rm = T),
            max = max(freight_prop, na.rm = T))

Preço vs. Avaliação do cliente

 analysis %>%
   mutate(log_price = log(price)) %>% 
   ggstatsplot::ggbetweenstats(
   x = high_review,
   y = log_price,
   title = "Log do Preço vs. Avaliação do Cliente")

Atraso na entrega vs. Avaliação do cliente

Aqui podemos perceber uma clara diferença, sendo essa uma variável que claramente impacta na Avaliação Máxima do cliente. A curva vermelha após o 0 indica que uma grande parte dos pedidos atrasados não recebe nota máxima.

analysis %>%
  filter(!is.na(high_review)) %>%
  ggplot(aes(x = delivery_delay, fill = as.factor(high_review))) +
  geom_density(alpha = 0.5)

Com poucas linhas de código, podemos perceber que 83,6% dos pedidos atrasados não foram avaliados com nota máxima

analysis %>%
  filter(!is.na(high_review)) %>%
  filter(delivery_delay > 0) %>%
  count(high_review) %>%
  mutate(prob = n / sum(n) * 100)

Variável binária de atraso vs. Avaliação do cliente

analysis %>% 
  mutate(late = ifelse(late == 1, 'Yes', 'No')) %>% 
  plota_tx_interesse(var_x = 'late',
                     var_y = 'high_review',
                     flag_interesse = 'Max. Score',
                     ylim = NA)

Quantidade de fotos vs. Avaliação do cliente

Antes de fazer essa análise, substituímos NA por 0 fotos. De qualquer maneira, não é possível notar qualquer relação.

analysis %>% 
  replace_na(list(product_photos_qty = 0)) %>% 
  plota_tx_interesse(var_x = 'product_photos_qty',
                     var_y = 'high_review',
                     flag_interesse = 'Max. Score')

Análise multivariada

Usando o facet_wrap do ggplot, é possível criar diversos gráficos com poucas linhas. Usaremos sempre as mesmas variáveis como X (Log do Preço) e Y (Log do Frete) para todos os gráficos, além de definir a avaliação (máxima ou não) pela cor dos pontos. Cria-se então um gráfico para cada combinação de estado + atraso (sim/não).

analysis %>%
  mutate(customer_state = fct_lump(customer_state, 5)) %>% 
  ggplot(aes(x = log(price),
             y = log(freight_value),
             col = high_review)) +
  geom_point(alpha = 0.5) +
  facet_wrap(customer_state ~ late, 
             labeller = "label_both") +
  ggtitle('Preço vs. Frete vs. Avaliação vs. Estado vs. Atraso') +
  theme_light()

Salvando a base de dados para modelagem

Seleção de variáveis

A variável model recebe uma versão modificada da nossa tabela completa de análise, utilizando apenas as colunas que podem ser necessárias em uma modelagem futura.

model <- analysis %>% 
  mutate(delivery_delay = as.numeric(delivery_delay),
         log_price = log(price),
         freight_prop_fix = (freight_value + 1) / (price + 1), # evitar -Inf
         log_freight_prop_fix = log(freight_prop_fix),
         product_category_name_cat = fct_lump(product_category_name, 7)
         ) %>% 
  replace_na(list(product_photos_qty = 0)) %>% 
  select(customer_state,
         log_price,
         log_freight_prop_fix,
         payment_type,
         product_category_name_cat,
         product_photos_qty,
         delivery_delay,
         late,
         high_review_binary)

Checagem final de NAs

Checamos a existência de NAs nessa nova base de dados, agora com menos colunas. Três colunas possuem NAs, então precisamos resolver isso antes de pensar em modelagem.

map_df(model, ~sum(is.na(.))) %>% 
  gather() %>% 
  arrange(desc(value))

Tratamento dos NAs

Para a coluna de categoria do produto, podemos criar uma categoria nomeada “NA” ao invés de simplesmente excluir todas as ocorrências. Para a coluna de avaliação, não há outra saída a não ser excluir. O mesmo se aplica ao único valor faltante do tipo de pagamento, totalizando 677 linhas a serem removidas. Um número relativamente baixo perante o total.

model <- model %>%
  filter(!is.na(high_review_binary)) %>%
  filter(!is.na(payment_type)) %>%
  mutate(product_category_name_cat = fct_na_value_to_level(product_category_name_cat))
map_df(model, ~sum(is.na(.))) %>% 
  gather() %>% 
  arrange(desc(value))
saveRDS(model, 'data/model.rds')
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoqKkltcG9ydGFuZG8gb3MgcGFjb3RlcyBlIGZ1bsOnw7VlcyoqDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzb3VyY2UoJ21pc2MvcGFja2FnZXMuUicpDQpzb3VyY2UoJ21pc2MvZnVuY3Rpb25zLlInKQ0KYGBgDQoNCiMgKipFREEgZG8gZGF0YXNldCBkYSBPbGlzdCBkZSBlLWNvbW1lcmNlIG5vIEJyYXNpbCoqDQoNCkVzdGUgY29uanVudG8gZGUgZGFkb3Mgw6kgdW1hIHJlcHJlc2VudGHDp8OjbyBkZSBpbmZvcm1hw6fDtWVzIGRldGFsaGFkYXMgc29icmUgMTAwIG1pbCBwZWRpZG9zIGZlaXRvcyBuYSBPbGlzdCBTdG9yZSBkdXJhbnRlIG8gcGVyw61vZG8gZGUgMjAxNiBhIDIwMTgsIGFicmFuZ2VuZG8gZGl2ZXJzYXMgcGxhdGFmb3JtYXMgZGUgdmVuZGEgbm8gQnJhc2lsLiBPcyByZWN1cnNvcyBwcmVzZW50ZXMgbmVzdGUgY29uanVudG8gZGUgZGFkb3Mgbm9zIHBlcm1pdGVtIGV4cGxvcmFyIG9zIHBlZGlkb3Mgc29iIGRpdmVyc2FzIHBlcnNwZWN0aXZhcywgZGVzZGUgbyBzdGF0dXMgZG8gcGVkaWRvLCBwcmXDp28sIHBhZ2FtZW50byBlIGRlc2VtcGVuaG8gZGUgZnJldGUsIGF0w6kgYSBsb2NhbGl6YcOnw6NvIGRvIGNsaWVudGUsIGF0cmlidXRvcyBkb3MgcHJvZHV0b3MgZSwgcG9yIGZpbSwgYXZhbGlhw6fDtWVzIGVzY3JpdGFzIHBlbG9zIGNsaWVudGVzLiDDiSBpbXBvcnRhbnRlIHJlc3NhbHRhciBxdWUgZXNzZXMgZGFkb3MgY29tZXJjaWFpcyBzw6NvIHJlYWlzLCBlbWJvcmEgdGVuaGFtIHNpZG8gYW5vbmltaXphZG9zLg0KDQpFc3NlIGNvbmp1bnRvIGRlIGRhZG9zIGZvaSBnZW50aWxtZW50ZSBmb3JuZWNpZG8gcGVsYSBPbGlzdCwgYSBtYWlvciBsb2phIGRlIGRlcGFydGFtZW50b3MgZW0gbWFya2V0cGxhY2VzIGJyYXNpbGVpcm9zLiBBIE9saXN0IGNvbmVjdGEgcGVxdWVub3MgbmVnw7NjaW9zIGRlIHRvZG8gbyBCcmFzaWwgYSBjYW5haXMgZGUgdmVuZGEgZGUgZm9ybWEgc2ltcGxpZmljYWRhLCBjb20gdW0gw7puaWNvIGNvbnRyYXRvLiBFc3NlcyBjb21lcmNpYW50ZXMgdMOqbSBhIGNhcGFjaWRhZGUgZGUgdmVuZGVyIHNldXMgcHJvZHV0b3MgYXRyYXbDqXMgZGEgT2xpc3QgU3RvcmUgZSBlbnZpw6EtbG9zIGRpcmV0YW1lbnRlIGFvcyBjbGllbnRlcyB1dGlsaXphbmRvIG9zIHBhcmNlaXJvcyBsb2fDrXN0aWNvcyBkYSBPbGlzdC4NCg0KTm8gc2lzdGVtYSBkYSBPbGlzdCBjYWRhIHBlZGlkbyDDqSBkZXNpZ25hZG8gYSB1bSAqKmN1c3RvbWVyaWQqKiDDum5pY28uIElzc28gc2lnbmlmaWNhIHF1ZSBjYWRhIGNvbnN1bWlkb3IgdGVyw6EgKipkaWZlcmVudGVzIGlkcyBwYXJhIGRpZmVyZW50ZXMgcGVkaWRvcyoqLiBPIHByb3DDs3NpdG8gZGUgdGVyIHVtICoqY3VzdG9tZXJ1bmlxdWVfaWQqKiDDum5pY28gcG9yIHBlc3NvYSAoY29tbyBzZSBmb3NzZSB1bSBDUEYpIG5hIGJhc2Ugw6kgcGVybWl0aXIgaWRlbnRpZmljYXIgY29uc3VtaWRvcmVzIHF1ZSBmaXplcmFtIHJlY29tcHJhcyBuYSBsb2phLiBDYXNvIGNvbnRyw6FyaW8sIHZvY8OqIGVuY29udHJhcmlhIHF1ZSBjYWRhIG9yZGVtIHNlbXByZSB0aXZlc3NlIGRpZmVyZW50ZXMgY29uc3VtaWRvcmVzIGFzc29jaWFkb3MuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpjdXN0b21lcnMgPC0gcmVhZF9jc3YoJ2RhdGEvb2xpc3RfY3VzdG9tZXJzX2RhdGFzZXQuY3N2JykNCm9yZGVycyA8LSByZWFkX2NzdignZGF0YS9vbGlzdF9vcmRlcnNfZGF0YXNldC5jc3YnKQ0KcmV2aWV3cyA8LSByZWFkX2NzdignZGF0YS9vbGlzdF9vcmRlcl9yZXZpZXdzX2RhdGFzZXQuY3N2JykNCnBheW1lbnRzIDwtIHJlYWRfY3N2KCdkYXRhL29saXN0X29yZGVyX3BheW1lbnRzX2RhdGFzZXQuY3N2JykNCnByb2R1Y3RzIDwtIHJlYWRfY3N2KCdkYXRhL29saXN0X3Byb2R1Y3RzX2RhdGFzZXQuY3N2JykNCnNlbGxlcnMgPC0gcmVhZF9jc3YoJ2RhdGEvb2xpc3Rfc2VsbGVyc19kYXRhc2V0LmNzdicpDQpjYXRlZ29yaWVzIDwtIHJlYWRfY3N2KCdkYXRhL3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZV90cmFuc2xhdGlvbi5jc3YnKQ0KZ2VvbG9jYXRpb24gPC0gcmVhZF9jc3YoJ2RhdGEvb2xpc3RfZ2VvbG9jYXRpb25fZGF0YXNldC5jc3YnKQ0Kb3JkZXJfaXRlbXMgPC0gcmVhZF9jc3YoJ2RhdGEvb2xpc3Rfb3JkZXJfaXRlbXNfZGF0YXNldC5jc3YnKQ0KDQojIEp1bnRhbmRvIG9zIGRhZG9zIGVtIHVtYSBiYXNlIMO6bmljYSBjb20gZXhjZcOnw6NvIGRvcyBkYWRvcyBkZSBnZW9sb2NhbGl6YcOnw6NvDQpjb21wbGV0ZV9kYXRhIDwtIGN1c3RvbWVycyAlPiUgDQogIGxlZnRfam9pbihvcmRlcnMpICU+JSANCiAgbGVmdF9qb2luKG9yZGVyX2l0ZW1zKSAlPiUgIyBVbWEgb3JkZW0gcG9kZSB0ZXIgbXVpdG8gaXRlbnMgZSBwb3IgaXNzbyBhIGJhc2UgZXhwYW5kZSBhcXVpDQogIGxlZnRfam9pbihyZXZpZXdzKSAlPiUgDQogIGxlZnRfam9pbihwYXltZW50cykgJT4lIA0KICBsZWZ0X2pvaW4ocHJvZHVjdHMpICU+JQ0KICBsZWZ0X2pvaW4oc2VsbGVycykgJT4lIA0KICBsZWZ0X2pvaW4oY2F0ZWdvcmllcykNCmBgYA0KDQojIyBQcmltZWlyYXMgb2JzZXJ2YcOnw7VlcyBub3MgZGFkb3MNCg0KYGBge3J9DQpza2ltKGNvbXBsZXRlX2RhdGEpDQpgYGANCg0KQSBhbsOhbGlzZSBwb3IgaXRlbSBleHBhbmRlIGEgYmFzZSwgbWFzIGEgbWFpb3JpYSBkb3MgcGVkaWRvcyAoODcuNiUpIGNvbnNpc3RlIGVtIGFwZW5hcyAxIGl0ZW0sIGNvbW8gasOhIGVyYSBkZSBzZSBlc3BlcmFyLiBQYXJhIHNpbXBsaWZpY2FyLCB2YW1vcyBsaW1pdGFyIG5vc3NhIGFuw6FsaXNlIHNvbWVudGUgYSBwZWRpZG9zIGNvbSB1bSBpdGVtLg0KDQpgYGB7cn0NCm9yZGVyX2l0ZW1zICU+JSANCiAgY291bnQob3JkZXJfaXRlbV9pZCkgJT4lICMgTsO6bWVybyBkZSBpdGVucyBkbyBtZXNtbyBwZWRpZG8NCiAgbXV0YXRlKHByb3AgPSByb3VuZChuIC8gc3VtKG4pICogMTAwLCAyKSkNCg0KY29tcGxldGVfZGF0YSA8LSBjb21wbGV0ZV9kYXRhICU+JSANCiAgZmlsdGVyKG9yZGVyX2l0ZW1faWQgPT0gMSkNCmBgYA0KDQojIyBUcmFuc2Zvcm1hbmRvIG9zIGRhZG9zDQoNCk5lc3RhIGV0YXBhLCBhcMOzcyBhIGFuw6FsaXNlIGluaWNpYWwgZGEgZXN0cnV0dXJhIGRvcyBkYWRvcywgY3JpYW1vcyB2YXJpw6F2ZWlzIHF1ZSBzZXLDo28gw7p0ZWlzIG5hIGFuw6FsaXNlIGUgcmVkdXppbW9zIG8gZXNjb3BvIGRhIGFuw6FsaXNlIHNvbWVudGUgcGFyYSBlbnRyZWdhcyBjb25jbHXDrWRhcyAob3JkZXJfc3RhdHVzID09ICdkZWxpdmVyZWQnKS4NCg0KLSAgIFZhcmnDoXZlbCBkZSByZXZpZXcgYWx0bzogIjEiIHNlIHNjb3JlIGRvIHJldmlldyBmb3IgNSwgZSAiMCIgY2FzbyBjb250csOhcmlvLg0KDQotICAgVmFyacOhdmVsIGRlIGRpZmVyZW7Dp2Egbm8gcHJhem8gZGUgZW50cmVnYTogZGlmZXJlbsOnYSBlbnRyZSBvIHByYXpvIGRlIGVudHJlZ2EgZXN0aXB1bGFkbyBwYXJhIG8gY2xpZW50ZSBlIGEgZGF0YSBkZSBlbnRyZWdhIHByb3ByaWFtZW50ZSBkaXRhLg0KDQotICAgVmFyacOhdmVsIGRlIHByb3BvcsOnw6NvIGRvIGZyZXRlOiByZWxhY2lvbmEgdmFsb3JlcyBkZSBGcmV0ZSBlIFByZcOnby4NCg0KLSAgIFZhcmnDoXZlbCBkZSBmcmV0ZSBncmF0dWl0bzogYmluw6FyaWENCg0KYGBge3J9DQphbmFseXNpcyA8LSBjb21wbGV0ZV9kYXRhICU+JSANCiAgbXV0YXRlKGhpZ2hfcmV2aWV3ID0gaWZlbHNlKHJldmlld19zY29yZSA9PSA1LCAnTWF4LiBTY29yZScsICdMZXNzIHRoYW4gNScpLA0KICAgICAgICAgaGlnaF9yZXZpZXdfYmluYXJ5ID0gaWZlbHNlKHJldmlld19zY29yZSA9PSA1LCAxLCAwKSwNCiAgICAgICAgIG9yZGVyX2RlbGl2ZXJlZF9jdXN0b21lcl9kYXRlID0gYXMuRGF0ZShvcmRlcl9kZWxpdmVyZWRfY3VzdG9tZXJfZGF0ZSksDQogICAgICAgICBvcmRlcl9lc3RpbWF0ZWRfZGVsaXZlcnlfZGF0ZSA9IGFzLkRhdGUob3JkZXJfZXN0aW1hdGVkX2RlbGl2ZXJ5X2RhdGUpLA0KICAgICAgICAgZGVsaXZlcnlfZGVsYXkgPSBhcy5udW1lcmljKG9yZGVyX2RlbGl2ZXJlZF9jdXN0b21lcl9kYXRlIC0gb3JkZXJfZXN0aW1hdGVkX2RlbGl2ZXJ5X2RhdGUpLA0KICAgICAgICAgbGF0ZSA9IGlmZWxzZShkZWxpdmVyeV9kZWxheSA+IDAsIDEsIDApLA0KICAgICAgICAgZnJlaWdodF9wcm9wID0gZnJlaWdodF92YWx1ZSAvIHByaWNlLA0KICAgICAgICAgZnJlZV9mcmVpZ2h0ID0gaWZlbHNlKGZyZWlnaHRfdmFsdWUgPT0gMCwgMSwgMCkpICU+JSANCiAgc2VsZWN0KGN1c3RvbWVyX3N0YXRlLCANCiAgICAgICAgIG9yZGVyX3N0YXR1cywgDQogICAgICAgICBwcmljZSwgDQogICAgICAgICBmcmVpZ2h0X3ZhbHVlLCANCiAgICAgICAgIHBheW1lbnRfdHlwZSwgDQogICAgICAgICBwYXltZW50X3ZhbHVlLCANCiAgICAgICAgIHByb2R1Y3RfY2F0ZWdvcnlfbmFtZSwNCiAgICAgICAgIHByb2R1Y3RfcGhvdG9zX3F0eSwNCiAgICAgICAgIHJldmlld19zY29yZSwNCiAgICAgICAgIGhpZ2hfcmV2aWV3LA0KICAgICAgICAgaGlnaF9yZXZpZXdfYmluYXJ5LA0KICAgICAgICAgZGVsaXZlcnlfZGVsYXksDQogICAgICAgICBsYXRlLA0KICAgICAgICAgb3JkZXJfZXN0aW1hdGVkX2RlbGl2ZXJ5X2RhdGUsDQogICAgICAgICBvcmRlcl9kZWxpdmVyZWRfY3VzdG9tZXJfZGF0ZSwNCiAgICAgICAgIHJldmlld19jb21tZW50X3RpdGxlLA0KICAgICAgICAgcmV2aWV3X2NvbW1lbnRfbWVzc2FnZSwNCiAgICAgICAgIGZyZWlnaHRfcHJvcCwNCiAgICAgICAgIGZyZWVfZnJlaWdodCwNCiAgICAgICAgIGN1c3RvbWVyX2NpdHksDQogICAgICAgICBzZWxsZXJfY2l0eSkgJT4lIA0KICBmaWx0ZXIob3JkZXJfc3RhdHVzID09ICdkZWxpdmVyZWQnKQ0KYGBgDQoNClRhbWLDqW0gcHJlY2lzYW1vcyBlbnRlbmRlciBhIGRpc3RyaWJ1acOnw6NvIGRvcyBkYWRvcyBmYWx0YW50ZXMgbm8gZGF0YXNldCwgcG9yIGlzc28gdXNhcmVtb3MgbyBwYWNvdGUgdmlzX2RhdC4gQ29tbyBub3NzYSBiYXNlIGRlIGRhZG9zIMOpIG11aXRvIGdyYW5kZSwgdmFtb3MgcGVnYXIgdW1hIHBhcnRlIG1lbm9yICgzMCUgZG8gdG90YWwpIGUgdGVudGFyIGVudGVuZGVyIHF1YWlzIGNvbHVuYXMgcG9zc3VlbSBtdWl0b3MgZGFkb3MgZmFsdGFuZG8uDQoNCk5hdHVyYWxtZW50ZSwgYXMgY29sdW5hcyBjb20gbWFpb3IgcXVhbnRpZGFkZSBkZSBkYWRvcyBmYWx0YW50ZXMgc8OjbyBhcyBxdWUgcG9zc3VlbSBwcmVlbmNoaW1lbnRvIG9wY2lvbmFsOiB0w610dWxvIGUgbWVuc2FnZW0gZG8gY29tZW50w6FyaW8gZGUgcmV2aXPDo28gZG8gcHJvZHV0by4NCg0KYGBge3J9DQpzZXQuc2VlZCgyMTAxKQ0KdmlzX2RhdChhbmFseXNpcyAlPiUgc2FtcGxlX2ZyYWMoMC4zKSkNCmBgYA0KDQpgYGB7cn0NCm1hcF9kZihhbmFseXNpcywgfnN1bShpcy5uYSguKSkpICU+JSANCiAgZ2F0aGVyKCkgJT4lIA0KICBhcnJhbmdlKGRlc2ModmFsdWUpKQ0KYGBgDQoNCk91dHJvIGZhdG8gcXVlIHByZWNpc2Ftb3MgY29ycmlnaXIgYW50ZXMgZGUgcXVhbHF1ZXIgYW7DoWxpc2Ugw6kgYSBleGlzdMOqbmNpYSBkZSB2YWxvcmVzIG51bG9zIG5hIGNvbHVuYSAibGF0ZSIsIHF1ZSDDqSB1bWEgY29sdW5hIGJpbsOhcmlhIGluZGljYW5kbyBhIHByZXNlbsOnYSBvdSBhdXPDqm5jaWEgZGUgdW0gYXRyYXNvIG5hIGVudHJlZ2EuIElzc28gb2NvcnJlIHBvcnF1ZSBhbGd1bnMgcGVkaWRvcyBuw6NvIHBvc3N1ZW0gZGF0YSBkZSBlbnRyZWdhLiBQb3IgaXNzbyBpcmVtb3MgcmVtb3ZlciBlc3NhcyBvY29ycsOqbmNpYXMuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgPC0gYW5hbHlzaXMgJT4lIA0KICBkcm9wX25hKGxhdGUpDQpgYGANCg0KUG9yIMO6bHRpbW8sIHZhbW9zIGNoZWNhciBhcyBjaWRhZGVzIGNvbSBtYWlzIHBlZGlkb3MgZW50cmVndWVzLg0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgY291bnQoY3VzdG9tZXJfY2l0eSkgJT4lDQogIG11dGF0ZShwcm9wID0gcm91bmQobiAvIHN1bShuKSAqIDEwMCwgMikpICU+JQ0KICBhcnJhbmdlKGRlc2MobikpDQpgYGANCg0KIyMgRXhwbG9yYcOnw6NvIGUgdmlzdWFsaXphw6fDo28gZGUgdmFyacOhdmVpcw0KDQojIyMgQXZhbGlhw6fDo28gZG8gcHJvZHV0byAoMSBhIDUpDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUNCiAgY291bnQocmV2aWV3X3Njb3JlKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gcmV2aWV3X3Njb3JlLCB5ID0gbikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9J2lkZW50aXR5JykgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbiksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMpDQpgYGANCg0KIyMjIE5vdGEgbcOheGltYTogc2ltIG91IG7Do28NCg0KYGBge3J9DQphbmFseXNpcyAlPiUNCiAgY291bnQoaGlnaF9yZXZpZXcpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBoaWdoX3JldmlldywgeSA9IG4pKSArDQogIGdlb21fYmFyKHN0YXQgPSdpZGVudGl0eScpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG4pLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzKQ0KYGBgDQoNCiMjIyBQZWRpZG9zIHBvciBVRg0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JQ0KICBjb3VudChjdXN0b21lcl9zdGF0ZSkgJT4lDQogIGdncGxvdChhZXMoeCA9IGN1c3RvbWVyX3N0YXRlLCB5ID0gbikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9J2lkZW50aXR5JykgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbiksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMpDQpgYGANCg0KIyMjIFBlZGlkb3MgcG9yIGZvcm1hIGRlIHBhZ2FtZW50bw0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JQ0KICBjb3VudChwYXltZW50X3R5cGUpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBwYXltZW50X3R5cGUsIHkgPSBuKSkgKw0KICBnZW9tX2JhcihzdGF0ID0naWRlbnRpdHknKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBuKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMykNCmBgYA0KDQojIyMgUGVkaWRvcyBwb3IgY2F0ZWdvcmlhIGRvIHByb2R1dG8NCg0KYGBge3J9DQphbmFseXNpcyAlPiUgDQogIGNvdW50KHByb2R1Y3RfY2F0ZWdvcnlfbmFtZSwgc29ydCA9IFQpICU+JSANCiAgbXV0YXRlKHByb3AgPSByb3VuZChuIC8gc3VtKG4pICogMTAwLCAyKSkNCmBgYA0KDQojIyMgQW7DoWxpc2UgZGUgcHJlw6dvcw0KDQpgYGB7cn0NCnN1bW1hcnkoYW5hbHlzaXMkcHJpY2UpDQpgYGANCg0KYGBge3J9DQpwMSA8LSBhbmFseXNpcyAlPiUgDQogIGdncGxvdChhZXMoeSA9IHByaWNlKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGdndGl0bGUoJ0JveHBsb3QnKQ0KDQpwMiA8LSBhbmFseXNpcyAlPiUgDQogIGdncGxvdChhZXMoeCA9IHByaWNlKSkgKw0KICBnZW9tX2RlbnNpdHkoKSArDQogIGdndGl0bGUoJ0RlbnNpZGFkZScpDQoNCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQ0KYGBgDQoNCkVzc2FzIGFuw6FsaXNlcyBub3MgaW5mb3JtYW0gcXVlIGEgdmFyacOhdmVsIFByZcOnbyDDqSBleHRyZW1hbWVudGUgYXNzaW3DqXRyaWNhLCBpbmNsdXNpdmUgY29tIG91dGxpZXJzIGNvbSBwcmXDp29zIGFjaW1hIGRlIFJcJCA2MDAwLiBPIG1haXMgY29tdW0gYXF1aSwgY2FzbyBzZWphIGRlc2Vqw6F2ZWwgaW5jbHXDrS1sYSBuYSBtb2RlbGFnZW0sIHNlcmlhIHRyYW5zZm9ybWFyIGVzc2EgdmFyacOhdmVsIGUgZXN0YWJpbGl6w6EtbGEuIFBvciBpc3NvLCBhIHBhcnRpciBkZSBhZ29yYSB1c2FyZW1vcyBvIGxvZ2FyaXRtbyBkYSB2YXJpw6F2ZWwgUHJlw6dvLg0KDQpMZW1icmFuZG8gcXVlIG8gQm94cGxvdCBwb2RlIHNlciBwcm9ibGVtw6F0aWNvIHBvciBuw6NvIHJlZmxldGlyIGEgZGlzdHJpYnVpw6fDo28gZG9zIGRhZG9zLiBWZXJlbW9zIGFkaWFudGUgdW0gZXhlbXBsbyBtZWxob3IsIGNvbSBWaW9saW4gUGxvdHMuDQoNCmBgYHtyfQ0KcDFsb2cgPC0gYW5hbHlzaXMgJT4lIA0KICBnZ3Bsb3QoYWVzKHkgPSBsb2cocHJpY2UpKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGdndGl0bGUoJ0JveHBsb3QnKQ0KDQpwMmxvZyA8LSBhbmFseXNpcyAlPiUgDQogIGdncGxvdChhZXMoeCA9IGxvZyhwcmljZSkpKSArDQogIGdlb21fZGVuc2l0eSgpICsNCiAgZ2d0aXRsZSgnRGVuc2lkYWRlJykNCg0KZ3JpZC5hcnJhbmdlKHAxbG9nLCBwMmxvZywgbnJvdyA9IDEpDQpgYGANCg0KIyMjIEFuw6FsaXNlIGRlIGZyZXRlDQoNCmBgYHtyfQ0Kc3VtbWFyeShhbmFseXNpcyRmcmVpZ2h0X3ZhbHVlKQ0KYGBgDQoNCjc1JSBkb3MgcHJvZHV0b3MgdGl2ZXJhbSB1bSBmcmV0ZSBpbmZlcmlvciBhIDIxIHJlYWlzLCBtYXMgw6kgcG9zc8OtdmVsIHZlciBxdWUgdW0gcGVkaWRvIGVtIGVzcGVjw61maWNvIHRldmUgdW0gZnJldGUgc3VwZXJpb3IgYSA0MDAgcmVhaXMhDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIGdncGxvdChhZXMoeCA9IGxvZyhmcmVpZ2h0X3ZhbHVlKSkpICsNCiAgZ2VvbV9kZW5zaXR5KCkgKw0KICBnZ3RpdGxlKCdWYXJpw6F2ZWwgRnJldGUgY29tIFRyYW5zZm9ybWHDp8OjbyBMb2dhcsOtdG1pY2EnKQ0KYGBgDQoNCkEgdmFyacOhdmVsIGRlIEZyZXRlIHBhcmVjZSBzZXIgbWFpcyBwcm9ibGVtw6F0aWNhLCBwb2lzIHRlbSB2YWxvcmVzIDAgZSBhbGd1bnMgc2FsdG9zLiBBIG1lbGhvciBtYW5laXJhIGRlIGlkZW50aWZpY2FyIHJhcGlkYW1lbnRlIG9uZGUgZXN0w6NvIGVzc2VzIHBvbnRvcyBkZSBjb3J0ZSDDqSBhdHJhdsOpcyBkZSBpbnRlcmF0aXZpZGFkZSBncsOhZmljYSENCg0KYGBge3J9DQooYW5hbHlzaXMgJT4lIA0KICBmaWx0ZXIoZnJlaWdodF92YWx1ZSA+IDApICU+JSAjIFRpcmEgb3MgZnJldGVzIGdyYXR1aXRvcw0KICBnZ3Bsb3QoYWVzKHggPSBmcmVpZ2h0X3ZhbHVlKSkgKw0KICBnZW9tX2RlbnNpdHkoKSkgJT4lIGdncGxvdGx5KCkNCmBgYA0KDQpPIHBvbnRvIG5vIGVudG9ybm8gZG8gMTAgcGFyZWNlIHVtIHBvbnRvIGRlIGF0ZW7Dp8OjbyBwZWxhIHN1YmlkYSDDrW5ncmVtZSBhcMOzcyBlbGUuIFRlbW9zIG11aXRvcyB2YWxvcmVzIGRlIGZyZXRlIHJlcGV0aWRvcy4gVGFsdmV6IHNlcmlhIGludGVyZXNzYW50ZSB0cmFuc2Zvcm1hciBlc3NhIHZhcmnDoXZlbCBlbSBjYXRlZ8OzcmljYS4NCg0KQWzDqW0gZGlzc28sIG8gZnJldGUgZGV2ZSB0ZXIgcmVsYcOnw6NvIGNvbSBhIGRpc3TDom5jaWEgZW50cmUgY2xpZW50ZSBlIHZlbmRlZG9yLCBlIHVtYSBhbsOhbGlzZSBmdXR1cmEgcG9kZXJpYSBsZXZhciBpc3NvIGVtIGNvbnNpZGVyYcOnw6NvIGZhemVuZG8gYWxndW0gdGlwbyBkZSBtYW5pcHVsYcOnw6NvIGRlIGxvY2FsaXphw6fDo28sIG1hcyB0YWx2ZXogb3MgZGFkb3MgbsOjbyBwZXJtaXRhbSBmYXplciBlc3NhIGFuw6FsaXNlIGRlIG1hbmVpcmEgZsOhY2lsLg0KDQojIyMgQW7DoWxpc2UgZGUgZnJldGUgc29icmUgcHJlw6dvDQoNCmBgYHtyfQ0Kc3VtbWFyeShhbmFseXNpcyRmcmVpZ2h0X3Byb3ApDQpgYGANCg0KQXF1aSBtYWlzIHVtYSB2ZXogdmVtb3MgdW1hIHByb3BvcsOnw6NvIGFic3VyZGEgZGUgZnJldGUgc29icmUgcHJlw6dvLiBFbSB1bSBwZWRpZG8gZXNwZWPDrWZpY28sIG8gZnJldGUgY29ycmVzcG9uZGV1IGEgMjEuNCUgZG8gdmFsb3IgdG90YWwgZGEgY29tcHJhLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYW5hbHlzaXMgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBsb2coZnJlaWdodF9wcm9wKSkpICsNCiAgZ2VvbV9kZW5zaXR5KCkNCmBgYA0KDQpPIGxvZ2FyaXRtbyBkZXNzYSB2YXJpw6F2ZWwsIHF1YW5kbyBvIGZyZXRlIMOpIHplcm8sIHJlc3VsdGEgZW0gLUluZmluaXRvLiBJc3NvIGNhdXNhcmlhIHByb2JsZW1hcyBuYSBob3JhIGRlIGNyaWFyIG9zIG1vZGVsb3MsIG1hcyBwb2RlbW9zIHJlc29sdmVyIGZhY2lsbWVudGU6IGJhc3RhIGFjcmVzY2VudGFyIHVtIHZhbG9yIGlycmlzw7NyaW8gYW8gZnJldGUsIGRlIG1vZG8gcXVlIGEgZGl2aXPDo28gbsOjbyByZXN1bHRlIGVtIHplcm8uDQoNCiMjIyBBbsOhbGlzZSBkZSBmcmV0ZSB4IHByZcOnbw0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYW5hbHlzaXMgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBsb2cocHJpY2UpLA0KICAgICAgICAgICAgIHkgPSBsb2coZnJlaWdodF92YWx1ZSkpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkNCmBgYA0KDQojIyMgRnJldGVzIGdyYXR1aXRvcw0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgY291bnQoZnJlZV9mcmVpZ2h0LCBzb3J0ID0gVCkgJT4lIA0KICBtdXRhdGUocHJvcCA9IG4gLyBzdW0obikpDQpgYGANCg0KT3MgcGVkaWRvcyBjb20gZnJldGUgZ3JhdHVpdG8gZXF1aXZhbGVtIGEgbWVub3MgZGUgMCw0JSBkbyB0b3RhbC4gw4kgdW1hIHBvcsOnw6NvIHF1YXNlIGlycmVsZXZhbnRlIGRvcyBkYWRvcywgZW50w6NvIHBvZGVtb3MgY29uc2lkZXJhciByZW1vdmVyIGVzc2VzIGRhZG9zIHBhcmEgZmFjaWxpdGFyIGEgbW9kZWxhZ2VtIGZ1dHVyYS4NCg0KIyMjIEFuw6FsaXNlIGRlIGF0cmFzb3MgbmFzIGVudHJlZ2FzDQoNCmBgYHtyfQ0Kc3VtbWFyeShhcy5udW1lcmljKGFuYWx5c2lzJGRlbGl2ZXJ5X2RlbGF5KSkNCmBgYA0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgY291bnQobGF0ZSkgJT4lIA0KICBtdXRhdGUocHJvcCA9IG4gLyBzdW0obikpDQpgYGANCg0KUG9kZW1vcyBwZXJjZWJlciBxdWUgNzUlIGRvcyBwZWRpZG9zIGNoZWdhcmFtIGNvbSBubyBtw61uaW1vIDcgZGlhcyBkZSBhbnRlY2Vkw6puY2lhLCBtYXMgZXhpc3RlbSB0YW1iw6ltIHBlZGlkb3MgY29tIGF0cmFzb3MgYWJzdXJkb3MsIGNoZWdhbmRvIGEgNiBtZXNlcy4gVGFtYsOpbSBkZXNjb2JyaW1vcyBxdWUgbWVub3MgZGUgNyUgZG9zIHBlZGlkb3MgYW5hbGlzYWRvcyBmb3JhbSBlbnRyZWd1ZXMgY29tIGF0cmFzby4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFuYWx5c2lzICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gZGVsaXZlcnlfZGVsYXkpKSArDQogIGdlb21fZGVuc2l0eSgpDQpgYGANCg0KQXF1aSBwb2RlbW9zIHZlciB1bWEgbGV2ZSBpbmNsaW5hw6fDo28gYSBwYXJ0aXIgZG8gMCwgaW5kaWNhbmRvIG9zIHBlZGlkb3MgYXRyYXNhZG9zLiBPIGluY2zDrW5pbyDDqSBiZW0gcGVxdWVubyBlIGrDoSBzYWJlbW9zIHF1ZSBob3V2ZXJhbSByZWxhdGl2YW1lbnRlIHBvdWNvcyBwZWRpZG9zIGF0cmFzYWRvcy4NCg0KIyMjIFF1YW50aWRhZGUgZGUgZm90b3MgZG8gcHJvZHV0bw0KDQpNdWl0b3MgcHJvZHV0b3MgZW0gbWFya2V0cGxhY2VzIGNvc3R1bWFtIGNvbnRhciBjb20gZm90b3MgaWx1c3RyYXRpdmFzIGRvIHByb2R1dG8sIHF1ZSBhanVkYW0gYSBndWlhciBvIGNsaWVudGUgbmEgaG9yYSBkYSBjb21wcmEuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lIA0KICBjb3VudChwcm9kdWN0X3Bob3Rvc19xdHkpICU+JSANCiAgbXV0YXRlKHByb3AgPSBuIC8gc3VtKG4pKQ0KYGBgDQoNCiMjIE3Dumx0aXBsYXMgZGVuc2lkYWRlcyBQcmXDp28gdnMuIENhdGVnb3JpYQ0KDQpWYW1vcyBhbmFsaXNhciBhIGRlbnNpZGFkZSBkbyBwcmXDp28gcG9yIGNhdGVnb3JpYS4gTm92YW1lbnRlIHVzYW1vcyAqKmZjdF9sdW1wKiogcGFyYSBhZ3J1cGFyIGFzIGNhdGVnb3JpYXMgZm9yYSBkbyB0b3AgNyBlbSBmcmVxdcOqbmNpYSBlIGNyaWFtb3MgdW0gZ3LDoWZpY28gY29tIHVtYSBjdXJ2YSBkZSBkZW5zaWRhZGUgcGFyYSBvIHByZcOnbyB2cy4gY2FkYSBjYXRlZ29yaWEuIENvbW8gYXMgY3VydmFzIHNlIHNvYnJlcMO1ZW0sIGEgdmlzdWFsaXphw6fDo28gw6kgZGlmaWN1bHRhZGEuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lIA0KICBtdXRhdGUoY2F0X3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZSA9IGZjdF9sdW1wKHByb2R1Y3RfY2F0ZWdvcnlfbmFtZSwgbiA9IDcpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGxvZyhwcmljZSksIGZpbGwgPSBjYXRfcHJvZHVjdF9jYXRlZ29yeV9uYW1lKSkgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjM1KQ0KYGBgDQoNClVtIG1vZG8gbWVsaG9yIGRlIHZpc3VhbGl6YXIgc2VyaWEgY29sb2NhciBhIGJhc2UgZGUgY2FkYSBjdXJ2YSBlbSB1bSBuw612ZWwgZGlmZXJlbnRlLiBPIGdyw6FmaWNvIGFjaW1hIG5vcyBtb3N0cmF2YSBhIGV4aXN0w6puY2lhIGRlIE5BcyBuYSBjb2x1bmEsIGVudMOjbyB2YW1vcyB0YW1iw6ltIHJlbW92ZXIuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lDQogIGZpbHRlcighaXMubmEocHJvZHVjdF9jYXRlZ29yeV9uYW1lKSkgJT4lIA0KICBtdXRhdGUoY2F0X3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZSA9IGZjdF9sdW1wKHByb2R1Y3RfY2F0ZWdvcnlfbmFtZSwgbiA9IDcpLA0KICAgICAgICAgbG9nX3ByaWNlID0gbG9nKHByaWNlKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IGxvZ19wcmljZSwgDQogICAgICAgICAgICAgeSA9IGNhdF9wcm9kdWN0X2NhdGVnb3J5X25hbWUsDQogICAgICAgICAgICAgZmlsbCA9IHN0YXQoeCkpKSArIA0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzX2dyYWRpZW50KHNjYWxlID0gMywgcmVsX21pbl9oZWlnaHQgPSAwLjAwMSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhuYW1lID0gIkxvZyBkbyBQcmXDp28iKQ0KYGBgDQoNCk91dHJvIG1vZG8gZGUgYW5hbGlzYXIgb3MgdmFsb3JlcyBwb3IgY2F0ZWdvcmlhIHNlcmlhIGNyaWFuZG8gdW0gYm94cGxvdCBwYXJhIGNhZGEsIHNhYmVuZG8gZGEgbGltaXRhw6fDo28gbmF0dXJhbCBkbyBib3hwbG90IGVtIG7Do28gcmVmbGV0aXIgYmVtIGEgZGlzdHJpYnVpw6fDo28vZnJlcXXDqm5jaWEgZG9zIGRhZG9zLg0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgZmlsdGVyKCFpcy5uYShwcm9kdWN0X2NhdGVnb3J5X25hbWUpKSAlPiUNCiAgbXV0YXRlKGNhdF9wcm9kdWN0X2NhdGVnb3J5X25hbWUgPSBmY3RfbHVtcChwcm9kdWN0X2NhdGVnb3J5X25hbWUsIG4gPSA3KSwNCiAgICAgICAgIGxvZ19wcmljZSA9IGxvZyhwcmljZSkpICU+JSANCiAgZ2dwbG90KGFlcyhmaWxsID0gY2F0X3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZSwNCiAgICAgICAgICAgICB5ID0gbG9nX3ByaWNlLA0KICAgICAgICAgICAgIHggPSBjYXRfcHJvZHVjdF9jYXRlZ29yeV9uYW1lKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSkpDQpgYGANCg0KQ29tIGFsZ3VtYXMgbXVkYW7Dp2FzIG5vIGPDs2RpZ28gYW50ZXJpb3IsIHBvZGVtb3MgdHJhbnNmb3JtYXIgb3MgYm94IHBsb3RzIGVtIHZpb2xpbiBwbG90cywgcXVlIG1vc3RyYW0gY29tIGNsYXJlemEgYSBkaXN0cmlidWnDp8OjbyBkb3MgZGFkb3MuIEEgbGltaXRhw6fDo28gbmF0dXJhbCBkbyB2aW9saW4gcGxvdCBubyBnZ3Bsb3QgZW0gUiDDqSBhIGF1c8OqbmNpYSBkYXMgbGluaGFzIHBlcmNlbnRpcywgZW50w6NvIHBvZGVtb3MgYWRpY2lvbmFyIHVtIGJveCBwbG90IGRlbnRybyBkZSBjYWRhIHZpb2xpbiBwbG90IGUgb2J0ZXIgbyAibWVsaG9yIGRvcyBkb2lzIG11bmRvcyINCg0KYGBge3J9DQphbmFseXNpcyAlPiUNCiAgZmlsdGVyKCFpcy5uYShwcm9kdWN0X2NhdGVnb3J5X25hbWUpKSAlPiUNCiAgbXV0YXRlKGNhdF9wcm9kdWN0X2NhdGVnb3J5X25hbWUgPSBmY3RfbHVtcChwcm9kdWN0X2NhdGVnb3J5X25hbWUsIG4gPSA3KSwNCiAgICAgICAgIGxvZ19wcmljZSA9IGxvZyhwcmljZSkpICU+JSANCiAgZ2dwbG90KGFlcyhmaWxsID0gY2F0X3Byb2R1Y3RfY2F0ZWdvcnlfbmFtZSwNCiAgICAgICAgICAgICB5ID0gbG9nX3ByaWNlLA0KICAgICAgICAgICAgIHggPSBjYXRfcHJvZHVjdF9jYXRlZ29yeV9uYW1lKSkgKw0KICBnZW9tX3Zpb2xpbih0cmltID0gRkFMU0UpICsgICMgdHJpbSA9IEZBTFNFIHNob3dzIHRoZSBmdWxsIGRpc3RyaWJ1dGlvbg0KICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNzUpKSArDQogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSkpDQpgYGANCg0KIyMgQW7DoWxpc2VzIGJpdmFyaWFkYXMNCg0KIyMjIEVzdGFkbyB2cy4gQXZhbGlhw6fDo28gZG8gY2xpZW50ZQ0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGhpZ2hfcmV2aWV3X2JpbmFyeSkpICU+JQ0KICBncm91cF9ieShjdXN0b21lcl9zdGF0ZSkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBuID0gbigpLA0KICAgIG1heF9yZXZpZXdzID0gc3VtKGhpZ2hfcmV2aWV3X2JpbmFyeSksDQogICAgbWF4X3Jldmlld3NfcHJvcCA9IG1lYW4oaGlnaF9yZXZpZXdfYmluYXJ5KSkgJT4lDQogIGFycmFuZ2UoZGVzYyhtYXhfcmV2aWV3c19wcm9wKSkNCmBgYA0KDQpNZXNtbyBTw6NvIFBhdWxvIHNlbmRvIGRlIG1hbmVpcmEgZGlzcGFyYWRhIG8gZXN0YWRvIGNvbSBtYWlzIHBlZGlkb3MsIHRhbWLDqW0gw6kgbyBlc3RhZG8gbWFpcyBzYXRpc2ZlaXRvOiA2MS43JSBkb3MgcGVkaWRvcyBmb3JhbSBhdmFsaWFkb3MgY29tIG5vdGEgbcOheGltYS4gVmFtb3MgYWdvcmEgdGVzdGFyIHVtYSB2aXN1YWxpemHDp8OjbyBpbnRlcmF0aXZhIGNvbSBQbG90bHkgcXVlIG5vcyBwZXJtaXRpcsOhIHZlciBleGF0YW1lbnRlIG8gbsO6bWVybyBkZSBwZWRpZG9zIHBvciBlc3RhZG8gZSBhICJ0YXhhIGRlIHNhdGlzZmHDp8OjbyIgZGUgY2FkYS4NCg0KQXF1aSB2ZW1vcyBxdWUgbyBlc3RhZG8gbWFpcyBpbnNhdGlzZmVpdG8gc2VyaWEgbyBNYXJhbmjDo28sIG9uZGUgYXBlbmFzIDQ4LjMlIGRvcyA3MzkgcGVkaWRvcyBmb3JhbSBhdmFsaWFkb3MgY29tIGEgbm90YSBtw6F4aW1hLiBEZSBxdWFscXVlciBtYW5laXJhLCBuw6NvIMOpIHBvc3PDrXZlbCB0cmHDp2FyIHVtIHBhcmFsZWxvIGVudHJlIGVzdGFkbyBlIHByb2JhYmlsaWRhZGUgZGUgYXZhbGlhw6fDo28gbcOheGltYS4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFuYWx5c2lzICU+JSANCiAgcGxvdGFfdHhfaW50ZXJlc3NlKHZhcl94ID0gJ2N1c3RvbWVyX3N0YXRlJywNCiAgICAgICAgICAgICAgICAgICAgIHZhcl95ID0gJ2hpZ2hfcmV2aWV3JywNCiAgICAgICAgICAgICAgICAgICAgIGZsYWdfaW50ZXJlc3NlID0gJ01heC4gU2NvcmUnKQ0KYGBgDQoNClZhbW9zIHRlbnRhciBtdWRhciBhIGVzY2FsYSBkYSB2aXN1YWxpemHDp8OjbyBwYXJhIG9idGVyIG1haW9yIGNsYXJlemEuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdjdXN0b21lcl9zdGF0ZScsDQogICAgICAgICAgICAgICAgICAgICB2YXJfeSA9ICdoaWdoX3JldmlldycsDQogICAgICAgICAgICAgICAgICAgICBmbGFnX2ludGVyZXNzZSA9ICdNYXguIFNjb3JlJywNCiAgICAgICAgICAgICAgICAgICAgIHlsaW0gPSBOQSkNCmBgYA0KDQojIyMgRm9ybWEgZGUgcGFnYW1lbnRvIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdwYXltZW50X3R5cGUnLA0KICAgICAgICAgICAgICAgICAgICAgdmFyX3kgPSAnaGlnaF9yZXZpZXcnLA0KICAgICAgICAgICAgICAgICAgICAgZmxhZ19pbnRlcmVzc2UgPSAnTWF4LiBTY29yZScsDQogICAgICAgICAgICAgICAgICAgICB5bGltID0gTkEpDQpgYGANCg0KIyMjIENhdGVnb3JpYSBkbyBwcm9kdXRvIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdwcm9kdWN0X2NhdGVnb3J5X25hbWUnLA0KICAgICAgICAgICAgICAgICAgICAgdmFyX3kgPSAnaGlnaF9yZXZpZXcnLA0KICAgICAgICAgICAgICAgICAgICAgZmxhZ19pbnRlcmVzc2UgPSAnTWF4LiBTY29yZScsDQogICAgICAgICAgICAgICAgICAgICB5bGltID0gTkEpDQpgYGANCg0KVGVtb3MgbXVpdGFzIGNsYXNzZXMgZGlmZXJlbnRlcyBlIGlzc28gYWNhYmEgY29tcGxpY2FuZG8gbXVpdG8gYSB2aXN1YWxpemHDp8OjbyBkb3MgZGFkb3MuIFBvciBpc3NvIHZhbW9zIHBlZ2FyIGFzIDEwIG1haXMgcHJlc2VudGVzIGUgYWdydXBhciBvIHJlc3RvIGVtIHVtYSAxMcKqIHZhcmnDoXZlbCwgY2hhbWFkYSBkZSAiT3RoZXIiLiBJc3NvIMOpIHBvc3PDrXZlbCBhdHJhdsOpcyBkYSBmdW7Dp8OjbyAqKmZjdF9sdW1wKiogYSBzZWd1aXINCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFuYWx5c2lzICU+JSANCiAgbXV0YXRlKHByb2R1Y3RfY2F0ZWdvcnlfbmFtZV9jYXQgPSBmY3RfbHVtcChwcm9kdWN0X2NhdGVnb3J5X25hbWUsIDEwKSkgJT4lIA0KICBwbG90YV90eF9pbnRlcmVzc2UodmFyX3ggPSAncHJvZHVjdF9jYXRlZ29yeV9uYW1lX2NhdCcsDQogICAgICAgICAgICAgICAgICAgICB2YXJfeSA9ICdoaWdoX3JldmlldycsDQogICAgICAgICAgICAgICAgICAgICBmbGFnX2ludGVyZXNzZSA9ICdNYXguIFNjb3JlJywNCiAgICAgICAgICAgICAgICAgICAgIHlsaW0gPSBOQSkNCmBgYA0KDQpBZ29yYSBjb20gbyBncsOhZmljbyBtdWl0byBtYWlzIGxlZ8OtdmVsLCBwb2RlbW9zIHBlcmNlYmVyIHJhcGlkYW1lbnRlIHF1ZSBhcyAzIGNhdGVnb3JpYXMgY29tIG1haW9yIHRheGEgZGUgc2F0aXNmYcOnw6NvIGRvIGNsaWVudGUgKGF2YWxpYcOnw6NvIDUgZXN0cmVsYXMpIHPDo286DQoNCi0gICBCcmlucXVlZG9zICg2Mi44JSkNCg0KLSAgIEJlbGV6YSBlIHNhw7pkZSAoNjIuMSUpDQoNCi0gICBFc3BvcnRlIGUgbGF6ZXIgKDYxLjQlKQ0KDQojIyMgUHJlw6dvIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lIA0KICBncm91cF9ieShoaWdoX3JldmlldykgJT4lIA0KICBzdW1tYXJpemUobiA9IG4oKSwNCiAgICAgICAgICAgIG1lYW4gPSBtZWFuKHByaWNlLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgc2QgPSBzZChwcmljZSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIG1pbiA9IG1pbihwcmljZSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIG1heCA9IG1heChwcmljZSwgbmEucm0gPSBUKSkNCmBgYA0KDQojIyMgRnJldGUgdnMuIEF2YWxpYcOnw6NvIGRvIGNsaWVudGUNCg0KYGBge3J9DQphbmFseXNpcyAlPiUgDQogIGdyb3VwX2J5KGhpZ2hfcmV2aWV3KSAlPiUgDQogIHN1bW1hcml6ZShuID0gbigpLA0KICAgICAgICAgICAgbWVhbiA9IG1lYW4oZnJlaWdodF92YWx1ZSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIHNkID0gc2QoZnJlaWdodF92YWx1ZSwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIG1pbiA9IG1pbihmcmVpZ2h0X3ZhbHVlLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgbWF4ID0gbWF4KGZyZWlnaHRfdmFsdWUsIG5hLnJtID0gVCkpDQpgYGANCg0KIyMjIEZyZXRlIHNvYnJlIHByZcOnbyB2cy4gQXZhbGlhw6fDo28gZG8gY2xpZW50ZQ0KDQpgYGB7cn0NCmFuYWx5c2lzICU+JSANCiAgZ3JvdXBfYnkoaGlnaF9yZXZpZXcpICU+JSANCiAgc3VtbWFyaXplKG4gPSBuKCksDQogICAgICAgICAgICBtZWFuID0gbWVhbihmcmVpZ2h0X3Byb3AsIG5hLnJtID0gVCksDQogICAgICAgICAgICBzZCA9IHNkKGZyZWlnaHRfcHJvcCwgbmEucm0gPSBUKSwNCiAgICAgICAgICAgIG1pbiA9IG1pbihmcmVpZ2h0X3Byb3AsIG5hLnJtID0gVCksDQogICAgICAgICAgICBtYXggPSBtYXgoZnJlaWdodF9wcm9wLCBuYS5ybSA9IFQpKQ0KYGBgDQoNCiMjIyBQcmXDp28gdnMuIEF2YWxpYcOnw6NvIGRvIGNsaWVudGUNCg0KYGBge3J9DQogYW5hbHlzaXMgJT4lDQogICBtdXRhdGUobG9nX3ByaWNlID0gbG9nKHByaWNlKSkgJT4lIA0KICAgZ2dzdGF0c3Bsb3Q6OmdnYmV0d2VlbnN0YXRzKA0KICAgeCA9IGhpZ2hfcmV2aWV3LA0KICAgeSA9IGxvZ19wcmljZSwNCiAgIHRpdGxlID0gIkxvZyBkbyBQcmXDp28gdnMuIEF2YWxpYcOnw6NvIGRvIENsaWVudGUiKQ0KYGBgDQoNCiMjIyBBdHJhc28gbmEgZW50cmVnYSB2cy4gQXZhbGlhw6fDo28gZG8gY2xpZW50ZQ0KDQpBcXVpIHBvZGVtb3MgcGVyY2ViZXIgdW1hIGNsYXJhIGRpZmVyZW7Dp2EsIHNlbmRvIGVzc2EgdW1hIHZhcmnDoXZlbCBxdWUgY2xhcmFtZW50ZSBpbXBhY3RhIG5hIEF2YWxpYcOnw6NvIE3DoXhpbWEgZG8gY2xpZW50ZS4gQSBjdXJ2YSB2ZXJtZWxoYSBhcMOzcyBvIDAgaW5kaWNhIHF1ZSB1bWEgZ3JhbmRlIHBhcnRlIGRvcyBwZWRpZG9zIGF0cmFzYWRvcyBuw6NvIHJlY2ViZSBub3RhIG3DoXhpbWEuDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lDQogIGZpbHRlcighaXMubmEoaGlnaF9yZXZpZXcpKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gZGVsaXZlcnlfZGVsYXksIGZpbGwgPSBhcy5mYWN0b3IoaGlnaF9yZXZpZXcpKSkgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpDQpgYGANCg0KQ29tIHBvdWNhcyBsaW5oYXMgZGUgY8OzZGlnbywgcG9kZW1vcyBwZXJjZWJlciBxdWUgKio4Myw2JSoqIGRvcyBwZWRpZG9zIGF0cmFzYWRvcyBuw6NvIGZvcmFtIGF2YWxpYWRvcyBjb20gbm90YSBtw6F4aW1hDQoNCmBgYHtyfQ0KYW5hbHlzaXMgJT4lDQogIGZpbHRlcighaXMubmEoaGlnaF9yZXZpZXcpKSAlPiUNCiAgZmlsdGVyKGRlbGl2ZXJ5X2RlbGF5ID4gMCkgJT4lDQogIGNvdW50KGhpZ2hfcmV2aWV3KSAlPiUNCiAgbXV0YXRlKHByb2IgPSBuIC8gc3VtKG4pICogMTAwKQ0KYGBgDQoNCiMjIyBWYXJpw6F2ZWwgYmluw6FyaWEgZGUgYXRyYXNvIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQphbmFseXNpcyAlPiUgDQogIG11dGF0ZShsYXRlID0gaWZlbHNlKGxhdGUgPT0gMSwgJ1llcycsICdObycpKSAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdsYXRlJywNCiAgICAgICAgICAgICAgICAgICAgIHZhcl95ID0gJ2hpZ2hfcmV2aWV3JywNCiAgICAgICAgICAgICAgICAgICAgIGZsYWdfaW50ZXJlc3NlID0gJ01heC4gU2NvcmUnLA0KICAgICAgICAgICAgICAgICAgICAgeWxpbSA9IE5BKQ0KYGBgDQoNCiMjIyBRdWFudGlkYWRlIGRlIGZvdG9zIHZzLiBBdmFsaWHDp8OjbyBkbyBjbGllbnRlDQoNCkFudGVzIGRlIGZhemVyIGVzc2EgYW7DoWxpc2UsIHN1YnN0aXR1w61tb3MgTkEgcG9yIDAgZm90b3MuIERlIHF1YWxxdWVyIG1hbmVpcmEsIG7Do28gw6kgcG9zc8OtdmVsIG5vdGFyIHF1YWxxdWVyIHJlbGHDp8Ojby4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmFuYWx5c2lzICU+JSANCiAgcmVwbGFjZV9uYShsaXN0KHByb2R1Y3RfcGhvdG9zX3F0eSA9IDApKSAlPiUgDQogIHBsb3RhX3R4X2ludGVyZXNzZSh2YXJfeCA9ICdwcm9kdWN0X3Bob3Rvc19xdHknLA0KICAgICAgICAgICAgICAgICAgICAgdmFyX3kgPSAnaGlnaF9yZXZpZXcnLA0KICAgICAgICAgICAgICAgICAgICAgZmxhZ19pbnRlcmVzc2UgPSAnTWF4LiBTY29yZScpDQpgYGANCg0KIyMgQW7DoWxpc2UgbXVsdGl2YXJpYWRhDQoNClVzYW5kbyBvICoqZmFjZXRfd3JhcCoqIGRvIGdncGxvdCwgw6kgcG9zc8OtdmVsIGNyaWFyIGRpdmVyc29zIGdyw6FmaWNvcyBjb20gcG91Y2FzIGxpbmhhcy4gVXNhcmVtb3Mgc2VtcHJlIGFzIG1lc21hcyB2YXJpw6F2ZWlzIGNvbW8gWCAoTG9nIGRvIFByZcOnbykgZSBZIChMb2cgZG8gRnJldGUpIHBhcmEgdG9kb3Mgb3MgZ3LDoWZpY29zLCBhbMOpbSBkZSBkZWZpbmlyIGEgYXZhbGlhw6fDo28gKG3DoXhpbWEgb3UgbsOjbykgcGVsYSBjb3IgZG9zIHBvbnRvcy4gQ3JpYS1zZSBlbnTDo28gdW0gZ3LDoWZpY28gcGFyYSBjYWRhIGNvbWJpbmHDp8OjbyBkZSBlc3RhZG8gKyBhdHJhc28gKHNpbS9uw6NvKS4NCg0KYGBge3J9DQphbmFseXNpcyAlPiUNCiAgbXV0YXRlKGN1c3RvbWVyX3N0YXRlID0gZmN0X2x1bXAoY3VzdG9tZXJfc3RhdGUsIDUpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGxvZyhwcmljZSksDQogICAgICAgICAgICAgeSA9IGxvZyhmcmVpZ2h0X3ZhbHVlKSwNCiAgICAgICAgICAgICBjb2wgPSBoaWdoX3JldmlldykpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBmYWNldF93cmFwKGN1c3RvbWVyX3N0YXRlIH4gbGF0ZSwgDQogICAgICAgICAgICAgbGFiZWxsZXIgPSAibGFiZWxfYm90aCIpICsNCiAgZ2d0aXRsZSgnUHJlw6dvIHZzLiBGcmV0ZSB2cy4gQXZhbGlhw6fDo28gdnMuIEVzdGFkbyB2cy4gQXRyYXNvJykgKw0KICB0aGVtZV9saWdodCgpDQpgYGANCg0KIyMgU2FsdmFuZG8gYSBiYXNlIGRlIGRhZG9zIHBhcmEgbW9kZWxhZ2VtDQoNCiMjIyBTZWxlw6fDo28gZGUgdmFyacOhdmVpcw0KDQpBIHZhcmnDoXZlbCBtb2RlbCByZWNlYmUgdW1hIHZlcnPDo28gbW9kaWZpY2FkYSBkYSBub3NzYSB0YWJlbGEgY29tcGxldGEgZGUgYW7DoWxpc2UsIHV0aWxpemFuZG8gYXBlbmFzIGFzIGNvbHVuYXMgcXVlIHBvZGVtIHNlciBuZWNlc3PDoXJpYXMgZW0gdW1hIG1vZGVsYWdlbSBmdXR1cmEuDQoNCmBgYHtyfQ0KbW9kZWwgPC0gYW5hbHlzaXMgJT4lIA0KICBtdXRhdGUoZGVsaXZlcnlfZGVsYXkgPSBhcy5udW1lcmljKGRlbGl2ZXJ5X2RlbGF5KSwNCiAgICAgICAgIGxvZ19wcmljZSA9IGxvZyhwcmljZSksDQogICAgICAgICBmcmVpZ2h0X3Byb3BfZml4ID0gKGZyZWlnaHRfdmFsdWUgKyAxKSAvIChwcmljZSArIDEpLCAjIGV2aXRhciAtSW5mDQogICAgICAgICBsb2dfZnJlaWdodF9wcm9wX2ZpeCA9IGxvZyhmcmVpZ2h0X3Byb3BfZml4KSwNCiAgICAgICAgIHByb2R1Y3RfY2F0ZWdvcnlfbmFtZV9jYXQgPSBmY3RfbHVtcChwcm9kdWN0X2NhdGVnb3J5X25hbWUsIDcpDQogICAgICAgICApICU+JSANCiAgcmVwbGFjZV9uYShsaXN0KHByb2R1Y3RfcGhvdG9zX3F0eSA9IDApKSAlPiUgDQogIHNlbGVjdChjdXN0b21lcl9zdGF0ZSwNCiAgICAgICAgIGxvZ19wcmljZSwNCiAgICAgICAgIGxvZ19mcmVpZ2h0X3Byb3BfZml4LA0KICAgICAgICAgcGF5bWVudF90eXBlLA0KICAgICAgICAgcHJvZHVjdF9jYXRlZ29yeV9uYW1lX2NhdCwNCiAgICAgICAgIHByb2R1Y3RfcGhvdG9zX3F0eSwNCiAgICAgICAgIGRlbGl2ZXJ5X2RlbGF5LA0KICAgICAgICAgbGF0ZSwNCiAgICAgICAgIGhpZ2hfcmV2aWV3X2JpbmFyeSkNCmBgYA0KDQojIyMgQ2hlY2FnZW0gZmluYWwgZGUgTkFzDQoNCkNoZWNhbW9zIGEgZXhpc3TDqm5jaWEgZGUgTkFzIG5lc3NhIG5vdmEgYmFzZSBkZSBkYWRvcywgYWdvcmEgY29tIG1lbm9zIGNvbHVuYXMuIFRyw6pzIGNvbHVuYXMgcG9zc3VlbSBOQXMsIGVudMOjbyBwcmVjaXNhbW9zIHJlc29sdmVyIGlzc28gYW50ZXMgZGUgcGVuc2FyIGVtIG1vZGVsYWdlbS4NCg0KYGBge3J9DQptYXBfZGYobW9kZWwsIH5zdW0oaXMubmEoLikpKSAlPiUgDQogIGdhdGhlcigpICU+JSANCiAgYXJyYW5nZShkZXNjKHZhbHVlKSkNCmBgYA0KDQojIyMgVHJhdGFtZW50byBkb3MgTkFzDQoNClBhcmEgYSBjb2x1bmEgZGUgY2F0ZWdvcmlhIGRvIHByb2R1dG8sIHBvZGVtb3MgY3JpYXIgdW1hIGNhdGVnb3JpYSBub21lYWRhICJOQSIgYW8gaW52w6lzIGRlIHNpbXBsZXNtZW50ZSBleGNsdWlyIHRvZGFzIGFzIG9jb3Jyw6puY2lhcy4gUGFyYSBhIGNvbHVuYSBkZSBhdmFsaWHDp8OjbywgbsOjbyBow6Egb3V0cmEgc2HDrWRhIGEgbsOjbyBzZXIgZXhjbHVpci4gTyBtZXNtbyBzZSBhcGxpY2EgYW8gw7puaWNvIHZhbG9yIGZhbHRhbnRlIGRvIHRpcG8gZGUgcGFnYW1lbnRvLCB0b3RhbGl6YW5kbyA2NzcgbGluaGFzIGEgc2VyZW0gcmVtb3ZpZGFzLiBVbSBuw7ptZXJvIHJlbGF0aXZhbWVudGUgYmFpeG8gcGVyYW50ZSBvIHRvdGFsLg0KDQpgYGB7cn0NCm1vZGVsIDwtIG1vZGVsICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGhpZ2hfcmV2aWV3X2JpbmFyeSkpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHBheW1lbnRfdHlwZSkpICU+JQ0KICBtdXRhdGUocHJvZHVjdF9jYXRlZ29yeV9uYW1lX2NhdCA9IGZjdF9uYV92YWx1ZV90b19sZXZlbChwcm9kdWN0X2NhdGVnb3J5X25hbWVfY2F0KSkNCmBgYA0KDQpgYGB7cn0NCm1hcF9kZihtb2RlbCwgfnN1bShpcy5uYSguKSkpICU+JSANCiAgZ2F0aGVyKCkgJT4lIA0KICBhcnJhbmdlKGRlc2ModmFsdWUpKQ0KYGBgDQoNCmBgYHtyfQ0Kc2F2ZVJEUyhtb2RlbCwgJ2RhdGEvbW9kZWwucmRzJykNCmBgYA0K